﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GameDataTempshitEditor.Widgets
{
    public class PropertyItemSettings
    {
        public Boolean ShowAdvancedProperties;

        public PropertyItemSettings()
        {
            ShowAdvancedProperties = false;
        }

        private static PropertyItemSettings _Default;

        public static PropertyItemSettings Default
        {
            get
            {
                if ( _Default == null )
                    _Default = new PropertyItemSettings();

                return _Default;
            }
        }
    }

    public abstract class PropertyItemBase
    {
        private int _Height;
        protected bool _CanExpand;
        private bool _IsExpanded;
        private List<PropertyItemBase> _Children;
        private List<PropertyItemAction> _Actions; // persistent actions
        private PropertyItemBase _Parent;
        private string _Name;
        private Rectangle _ValueRect; // hack
        private PropertyItemSettings _Settings;

        public int Height { get { return _Height; } }
        public bool CanExpand { get { return _CanExpand; } }
        public bool IsExpanded { get { return _IsExpanded; } }
        public List<PropertyItemBase> Children { get { return _Children; } }
        public PropertyItemBase Parent { get { return _Parent; } }
        public string Name { get { return _Name; } }
        public Rectangle ValueRect { get { return _ValueRect; } }

        public PropertyItemBase(PropertyItemBase parent, string name)
        {
            _Name = name;
            _Parent = parent;
            _Height = 20;
            _CanExpand = false;
            _IsExpanded = false;
            _Children = new List<PropertyItemBase>();
            _ValueRect = new Rectangle();
        }

        protected static Rectangle CalcCenterRect(Rectangle area, int width, int height)
        {
            Rectangle centerRect = new Rectangle(0, 0, width, height);
            centerRect.X = area.X + (area.Width - centerRect.Width) / 2;
            centerRect.Y = area.Y + (area.Height - centerRect.Height) / 2;
            return centerRect;
        }

        protected static RectangleF MakeRectangleF(Rectangle rect)
        {
            return new RectangleF(rect.X, rect.Y, rect.Width, rect.Height);
        }

        private Rectangle CalcExpandIconRect(DataPropertyGridDrawStyle style, int xSize, int xMargin, int yOffset)
        {
            return CalcCenterRect(new Rectangle(xMargin, yOffset, style.ItemMargin, _Height), style.ExpandIconSize, style.ExpandIconSize);
        }

        private Rectangle CalcNameRect(DataPropertyGridDrawStyle style, int xSize, int xMargin, int yOffset)
        {
            int left = xMargin + style.ItemMargin;

            if (HasValue())
                return new Rectangle(left, yOffset, Math.Max(0, style.XSplit - left), _Height);
            else
                return new Rectangle(left, yOffset, Math.Max(0, xSize - left), _Height);
        }

        private Rectangle CalcValueRect(DataPropertyGridDrawStyle style, int xSize, int xMargin, int yOffset)
        {
            return new Rectangle(style.XSplit + 5, yOffset, Math.Max(0, xSize - (style.XSplit+4)), _Height);
        }

        private Rectangle CalcActionRect(DataPropertyGridDrawStyle style, ref int xSize, int xMargin, int yOffset)
        {
            var ret = new Rectangle(xSize - style.ItemActionSize, yOffset, style.ItemActionSize, style.ItemActionSize );
            xSize -= style.ItemActionSize + style.ItemActionMargin;
            return ret;
        }

        public void Render(Graphics g, DataPropertyGridDrawStyle style, int xSize, int xMargin, ref int yOffset)
        {
            RenderElement(g, style, xSize, xMargin, yOffset);
            yOffset += _Height;

            if (_IsExpanded)
            {
                xMargin += style.XMargin;

                foreach (var child in _Children)
                    child.Render(g, style, xSize, xMargin, ref yOffset);

                xMargin -= style.XMargin;
            }
        }

        public void RenderElement(Graphics g, DataPropertyGridDrawStyle style, int xSize, int xMargin, int yOffset)
        {
            // is this node selected ?
            bool isSelected = (style.Selection == this);

            // background - full element
            {
                Rectangle background = new Rectangle(0, yOffset, xSize, _Height);
                RenderBackground(g, style, background, isSelected);
            }

            // draw the expand icon
            if (_CanExpand)
            {
                Rectangle iconRect = CalcExpandIconRect(style, xSize, xMargin, yOffset);
                RenderExpandIcon(g, style, iconRect);
            }

            // name
            {
                Rectangle nameRect = CalcNameRect(style, xSize, xMargin, yOffset);
                RenderName(g, style, nameRect, isSelected);
            }

            // local value part
            {
                Rectangle valueRect = CalcValueRect(style, xSize, xMargin, yOffset);

                // value
                if (HasValue())
                {
                    // the value placement
                    RenderValue(g, style, valueRect, isSelected);

                    // separator between name and value
                    g.DrawLine(style.GridPen, style.XSplit, yOffset, style.XSplit, yOffset + _Height);
                }

                // move the value editor
                _ValueRect = valueRect;

            }

            // bottom line
            g.DrawLine(style.GridPen, 0, yOffset, xSize, yOffset);
        }

        public void RenderExpandIcon(Graphics g, DataPropertyGridDrawStyle style, Rectangle rect)
        {
            g.FillRectangle(style.ExpandIconBrush, rect);
            g.DrawRectangle(style.ExpandIconPen, rect);

            g.DrawLine(style.ExpandIconPen, rect.X + 2, rect.Y + rect.Height / 2, rect.X + rect.Width - 2, rect.Y + rect.Height / 2);

            if (!_IsExpanded)
                g.DrawLine(style.ExpandIconPen, rect.X + rect.Width / 2, rect.Y + 2, rect.X + rect.Width / 2, rect.Y + rect.Height - 2);
        }

        public virtual Brush GetBackgroundBrush(DataPropertyGridDrawStyle style, bool isSelected)
        {
            var brush = style.BackgroundBrushNormal;

            if (isSelected)
                brush = style.SelectionRectBrush;

            return brush;
        }

        public void RenderBackground(Graphics g, DataPropertyGridDrawStyle style, Rectangle rect, bool isSelected)
        {
            var brush = GetBackgroundBrush(style, isSelected);

            var errors = HasErrors();
            if (errors)
                brush = style.BackgroundBrushError;

            g.FillRectangle(brush, rect);

            if (isSelected && errors)
            {
                var innerRect = rect;
                innerRect.Offset(-1,-1);
                g.DrawRectangle(style.BackgroundPenError, innerRect);
            }
        }

        public virtual bool IsOverriden()
        {
            return false;
        }

        public virtual bool IsReadOnly()
        {
            return false;
        }

        public virtual bool IsStrikedOut()
        {
            return false;
        }

        public virtual bool IsItalic()
        {
            return false;
        }

        public virtual bool HasValue()
        {
            return true;
        }

        public virtual bool HasErrors()
        {
            return false;
        }

        public virtual string DisplayValue()
        {
            return "";
        }

        public PropertyItemSettings Settings
        {
            get
            {
                if ( _Settings != null )
                    return _Settings;

                if (_Parent != null)
                    return _Parent.Settings;

                return PropertyItemSettings.Default;
            }
            set
            {
                _Settings = value;
            }
        }

        public virtual bool SetValue(PropertyItemEditor fromEditor, string value, ref string errorMessage)
        {
            return false;
        }

        public virtual PropertyItemEditor CreateEditor()
        {
            return null;
        }

        private Font GetRenderFont(DataPropertyGridDrawStyle style)
        {
            if (IsStrikedOut())
                return style.TextCrossedFont;
            else if (IsItalic())
                return style.TextItalicFont;
            else if (IsOverriden())
                return style.TextBoldFont;

            return style.TextGeneralFont;
        }

        public void RenderName(Graphics g, DataPropertyGridDrawStyle style, Rectangle rect, bool isSelected)
        {
            var textBrush = isSelected ? style.TextBrushSelectedInv : (IsReadOnly() ? style.TextBrushGrayedOut : style.TextBrushNormal);
            g.DrawString(_Name, GetRenderFont(style), textBrush, MakeRectangleF(rect), style.TextFormatNormal);
        }

        public void RenderValue(Graphics g, DataPropertyGridDrawStyle style, Rectangle rect, bool isSelected)
        {
            var textBrush = isSelected ? style.TextBrushSelectedInv : (IsReadOnly() ? style.TextBrushGrayedOut : style.TextBrushNormal);
            g.DrawString(DisplayValue(), GetRenderFont(style), textBrush, MakeRectangleF(rect), style.TextFormatNormal);
        }

        public void Linearize(ref List<PropertyItemBase> outList)
        {
            outList.Add(this);

            if (_IsExpanded)
                foreach (var child in _Children)
                    child.Linearize(ref outList);
        }

        public PropertyItemAction HitTestAction(int x, int y, DataPropertyGridDrawStyle style, int xSize, int xMargin, ref int yOffset)
        {
            // test element
            if (_CanExpand)
            {
                var expandRect = CalcExpandIconRect(style, xSize, xMargin, yOffset);
                if (expandRect.Contains(x, y))
                    return new PropertyItemAction_ExpandButton(this);
            }

            // advance
            int orgYOffset = yOffset;
            yOffset += _Height;

            // test children
            if (_IsExpanded)
            {
                xMargin += style.XMargin;
                foreach (var child in _Children)
                {
                    var ret = child.HitTestAction(x, y, style, xSize, xMargin, ref yOffset);
                    if (ret != null)
                        return ret;
                }
                xMargin -= style.XMargin;
            }

            // if everything fails, select the item
            if (x >= 0 && x < xSize && y >= orgYOffset && y < (orgYOffset + _Height))
            {
                return new PropertyItemAction_Select(this);
            }

            // nothing
            return null;
        }

        public bool Collapse()
        {
            if (_IsExpanded)
            {
                if (CollapseElement())
                {
                    _IsExpanded = false;
                    return true;
                }
            }

            // failed to collapse
            return false;
        }

        public virtual bool CollapseElement()
        {
            _Children.Clear();
            return true;
        }

        public virtual bool ExpandElement( ref List<PropertyItemBase> newElements)
        {
            return false;
        }

        public bool Expand()
        {
            if (_CanExpand && !_IsExpanded)
            {
                List<PropertyItemBase> newElements = new List<PropertyItemBase>();
                if (ExpandElement(ref newElements))
                {
                    foreach (var newChild in newElements )
                        _Children.Add(newChild);

                    _IsExpanded = true;
                    return true;
                }
            }

            // failed to expand
            return false;
        }

        /// <summary>
        /// Describes reaction to the refresh request on the item
        /// Note that some items may change because of the refresh or even Collapse
        /// </summary>
        public enum RefreshResult
        {
            /// <summary>
            /// Refresh was not needed, values were fine, continue to hierarchy
            /// </summary>
            NoRefresh = 1,

            /// <summary>
            /// Refresh was done for the item and theres still a need to update the hierarchy, continue to children
            /// </summary>
            ItemRefreshed = 2, 

            /// <summary>
            /// Refresh was done for the item and THE WHOLE SUBTREE, do not recurse to children
            /// </summary>
            TreeRefreshed = 3,            
        }

        /// <summary>
        /// Perform refresh of this item
        /// </summary>
        /// <returns></returns>
        public virtual RefreshResult RefreshThis()
        {
            // by default we have nothing to refresh here
            return RefreshResult.NoRefresh;
        }

        /// <summary>
        /// Hierarchial refresh
        /// </summary>
        public void Refresh()
        {
            var result = RefreshThis();

            if (result != RefreshResult.TreeRefreshed)
            {
                foreach (var child in _Children)
                    child.Refresh();
            }
        }

        public List<PropertyItemAction> CreateActions()
        {
            List<PropertyItemAction> ret = new List<PropertyItemAction>();
            CreateElementActions(ref ret);
            return ret;
        }

        public virtual void CreateElementActions(ref List<PropertyItemAction> actions)
        {
            // derived
        }
    }
}
